/*
 * Home3DAttributesController.java 25 juin 07
 *
 * Sweet Home 3D, Copyright (c) 2007 Emmanuel PUYBARET / eTeks <info@eteks.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package com.eteks.sweethome3d.viewcontroller;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoableEdit;
import javax.swing.undo.UndoableEditSupport;

import com.eteks.sweethome3d.model.Home;
import com.eteks.sweethome3d.model.HomeEnvironment;
import com.eteks.sweethome3d.model.HomeTexture;
import com.eteks.sweethome3d.model.UserPreferences;

/**
 * A MVC controller for home 3D attributes view.
 * @author Emmanuel Puybaret
 */
public class Home3DAttributesController implements Controller
{
	/**
	 * The properties that may be edited by the view associated to this controller. 
	 */
	public enum Property
	{
		GROUND_COLOR, GROUND_PAINT, SKY_COLOR, SKY_PAINT, LIGHT_COLOR, WALLS_ALPHA, WALLS_TOP_COLOR
	}
	/**
	 * The possible values for {@linkplain #getGroundPaint() ground paint type}.
	 */
	public enum EnvironmentPaint
	{
		COLORED, TEXTURED
	}
	
	private final Home home;
	private final UserPreferences preferences;
	private final ViewFactory viewFactory;
	private final ContentManager contentManager;
	private final UndoableEditSupport undoSupport;
	private TextureChoiceController groundTextureController;
	private TextureChoiceController skyTextureController;
	private final PropertyChangeSupport propertyChangeSupport;
	private DialogView home3DAttributesView;
	
	private int groundColor;
	private EnvironmentPaint groundPaint;
	private int skyColor;
	private EnvironmentPaint skyPaint;
	private int lightColor;
	private float wallsAlpha;
	
	/**
	 * Creates the controller of 3D view with undo support.
	 */
	public Home3DAttributesController(Home home, UserPreferences preferences, ViewFactory viewFactory,
			ContentManager contentManager, UndoableEditSupport undoSupport)
	{
		this.home = home;
		this.preferences = preferences;
		this.viewFactory = viewFactory;
		this.contentManager = contentManager;
		this.undoSupport = undoSupport;
		this.propertyChangeSupport = new PropertyChangeSupport(this);
		
		updateProperties();
	}
	
	/**
	 * Returns the texture controller of the ground.
	 */
	public TextureChoiceController getGroundTextureController()
	{
		// Create sub controller lazily only once it's needed
		if (this.groundTextureController == null)
		{
			this.groundTextureController = new TextureChoiceController(
					this.preferences.getLocalizedString(Home3DAttributesController.class, "groundTextureTitle"),
					this.preferences, this.viewFactory, this.contentManager);
			this.groundTextureController.addPropertyChangeListener(TextureChoiceController.Property.TEXTURE,
					new PropertyChangeListener()
					{
						public void propertyChange(PropertyChangeEvent ev)
						{
							setGroundPaint(EnvironmentPaint.TEXTURED);
						}
					});
		}
		return this.groundTextureController;
	}
	
	/**
	 * Returns the texture controller of the sky.
	 */
	public TextureChoiceController getSkyTextureController()
	{
		// Create sub controller lazily only once it's needed
		if (this.skyTextureController == null)
		{
			this.skyTextureController = new TextureChoiceController(
					this.preferences.getLocalizedString(Home3DAttributesController.class, "skyTextureTitle"), false,
					this.preferences, this.viewFactory, this.contentManager);
			this.skyTextureController.addPropertyChangeListener(TextureChoiceController.Property.TEXTURE,
					new PropertyChangeListener()
					{
						public void propertyChange(PropertyChangeEvent ev)
						{
							setSkyPaint(EnvironmentPaint.TEXTURED);
						}
					});
		}
		return this.skyTextureController;
	}
	
	/**
	 * Returns the view associated with this controller.
	 */
	public DialogView getView()
	{
		// Create view lazily only once it's needed
		if (this.home3DAttributesView == null)
		{
			this.home3DAttributesView = this.viewFactory.createHome3DAttributesView(this.preferences, this);
		}
		return this.home3DAttributesView;
	}
	
	/**
	 * Displays the view controlled by this controller.
	 */
	public void displayView(View parentView)
	{
		getView().displayView(parentView);
	}
	
	/**
	 * Adds the property change <code>listener</code> in parameter to this controller.
	 */
	public void addPropertyChangeListener(Property property, PropertyChangeListener listener)
	{
		this.propertyChangeSupport.addPropertyChangeListener(property.name(), listener);
	}
	
	/**
	 * Removes the property change <code>listener</code> in parameter from this controller.
	 */
	public void removePropertyChangeListener(Property property, PropertyChangeListener listener)
	{
		this.propertyChangeSupport.removePropertyChangeListener(property.name(), listener);
	}
	
	/**
	 * Updates edited properties from the 3D attributes of the home edited by this controller.
	 */
	protected void updateProperties()
	{
		HomeEnvironment homeEnvironment = this.home.getEnvironment();
		setGroundColor(homeEnvironment.getGroundColor());
		HomeTexture groundTexture = homeEnvironment.getGroundTexture();
		getGroundTextureController().setTexture(groundTexture);
		if (groundTexture != null)
		{
			setGroundPaint(EnvironmentPaint.TEXTURED);
		}
		else
		{
			setGroundPaint(EnvironmentPaint.COLORED);
		}
		setSkyColor(homeEnvironment.getSkyColor());
		HomeTexture skyTexture = homeEnvironment.getSkyTexture();
		getSkyTextureController().setTexture(skyTexture);
		if (skyTexture != null)
		{
			setSkyPaint(EnvironmentPaint.TEXTURED);
		}
		else
		{
			setSkyPaint(EnvironmentPaint.COLORED);
		}
		setLightColor(homeEnvironment.getLightColor());
		setWallsAlpha(homeEnvironment.getWallsAlpha());
	}
	
	/**
	 * Sets the edited ground color.
	 */
	public void setGroundColor(int groundColor)
	{
		if (groundColor != this.groundColor)
		{
			int oldGroundColor = this.groundColor;
			this.groundColor = groundColor;
			this.propertyChangeSupport.firePropertyChange(Property.GROUND_COLOR.name(), oldGroundColor, groundColor);
			
			setGroundPaint(EnvironmentPaint.COLORED);
		}
	}
	
	/**
	 * Returns the edited ground color.
	 */
	public int getGroundColor()
	{
		return this.groundColor;
	}
	
	/**
	 * Sets whether the ground is colored or textured.
	 */
	public void setGroundPaint(EnvironmentPaint groundPaint)
	{
		if (groundPaint != this.groundPaint)
		{
			EnvironmentPaint oldGroundPaint = this.groundPaint;
			this.groundPaint = groundPaint;
			this.propertyChangeSupport.firePropertyChange(Property.GROUND_PAINT.name(), oldGroundPaint, groundPaint);
		}
	}
	
	/**
	 * Returns whether the ground is colored or textured.
	 */
	public EnvironmentPaint getGroundPaint()
	{
		return this.groundPaint;
	}
	
	/**
	 * Sets the edited sky color.
	 */
	public void setSkyColor(int skyColor)
	{
		if (skyColor != this.skyColor)
		{
			int oldSkyColor = this.skyColor;
			this.skyColor = skyColor;
			this.propertyChangeSupport.firePropertyChange(Property.SKY_COLOR.name(), oldSkyColor, skyColor);
		}
	}
	
	/**
	 * Returns the edited sky color.
	 */
	public int getSkyColor()
	{
		return this.skyColor;
	}
	
	/**
	 * Sets whether the sky is colored or textured.
	 */
	public void setSkyPaint(EnvironmentPaint skyPaint)
	{
		if (skyPaint != this.skyPaint)
		{
			EnvironmentPaint oldSkyPaint = this.skyPaint;
			this.skyPaint = skyPaint;
			this.propertyChangeSupport.firePropertyChange(Property.SKY_PAINT.name(), oldSkyPaint, skyPaint);
		}
	}
	
	/**
	 * Returns whether the sky is colored or textured.
	 */
	public EnvironmentPaint getSkyPaint()
	{
		return this.skyPaint;
	}
	
	/**
	 * Sets the edited light color.
	 */
	public void setLightColor(int lightColor)
	{
		if (lightColor != this.lightColor)
		{
			int oldLightColor = this.lightColor;
			this.lightColor = lightColor;
			this.propertyChangeSupport.firePropertyChange(Property.LIGHT_COLOR.name(), oldLightColor, lightColor);
		}
	}
	
	/**
	 * Returns the edited light color.
	 */
	public int getLightColor()
	{
		return this.lightColor;
	}
	
	/**
	 * Sets the edited walls transparency alpha.
	 */
	public void setWallsAlpha(float wallsAlpha)
	{
		if (wallsAlpha != this.wallsAlpha)
		{
			float oldWallsAlpha = this.wallsAlpha;
			this.wallsAlpha = wallsAlpha;
			this.propertyChangeSupport.firePropertyChange(Property.WALLS_ALPHA.name(), oldWallsAlpha, wallsAlpha);
		}
	}
	
	/**
	 * Returns the edited walls transparency alpha.
	 */
	public float getWallsAlpha()
	{
		return this.wallsAlpha;
	}
	
	/**
	 * Controls the modification of the 3D attributes of the edited home.
	 */
	public void modify3DAttributes()
	{
		int groundColor = getGroundColor();
		HomeTexture groundTexture = getGroundPaint() == EnvironmentPaint.TEXTURED
				? getGroundTextureController().getTexture() : null;
		int skyColor = getSkyColor();
		HomeTexture skyTexture = getSkyPaint() == EnvironmentPaint.TEXTURED ? getSkyTextureController().getTexture()
				: null;
		int lightColor = getLightColor();
		float wallsAlpha = getWallsAlpha();
		
		HomeEnvironment homeEnvironment = this.home.getEnvironment();
		int oldGroundColor = homeEnvironment.getGroundColor();
		HomeTexture oldGroundTexture = homeEnvironment.getGroundTexture();
		int oldSkyColor = homeEnvironment.getSkyColor();
		HomeTexture oldSkyTexture = homeEnvironment.getSkyTexture();
		int oldLightColor = homeEnvironment.getLightColor();
		float oldWallsAlpha = homeEnvironment.getWallsAlpha();
		
		// Apply modification
		doModify3DAttributes(home, groundColor, groundTexture, skyColor, skyTexture, lightColor, wallsAlpha);
		if (this.undoSupport != null)
		{
			UndoableEdit undoableEdit = new Home3DAttributesModificationUndoableEdit(this.home, this.preferences,
					oldGroundColor, oldGroundTexture, oldSkyColor, oldSkyTexture, oldLightColor, oldWallsAlpha,
					groundColor, groundTexture, skyColor, skyTexture, lightColor, wallsAlpha);
			this.undoSupport.postEdit(undoableEdit);
		}
	}
	
	/**
	 * Undoable edit for 3D attributes modification. This class isn't anonymous to avoid
	 * being bound to controller and its view.
	 */
	private static class Home3DAttributesModificationUndoableEdit extends AbstractUndoableEdit
	{
		private final Home home;
		private final UserPreferences preferences;
		private final int oldGroundColor;
		private final HomeTexture oldGroundTexture;
		private final int oldSkyColor;
		private final HomeTexture oldSkyTexture;
		private final int oldLightColor;
		private final float oldWallsAlpha;
		private final int groundColor;
		private final HomeTexture groundTexture;
		private final int skyColor;
		private final HomeTexture skyTexture;
		private final int lightColor;
		private final float wallsAlpha;
		
		private Home3DAttributesModificationUndoableEdit(Home home, UserPreferences preferences, int oldGroundColor,
				HomeTexture oldGroundTexture, int oldSkyColor, HomeTexture oldSkyTexture, int oldLightColor,
				float oldWallsAlpha, int groundColor, HomeTexture groundTexture, int skyColor, HomeTexture skyTexture,
				int lightColor, float wallsAlpha)
		{
			this.home = home;
			this.preferences = preferences;
			this.oldGroundColor = oldGroundColor;
			this.oldGroundTexture = oldGroundTexture;
			this.oldSkyColor = oldSkyColor;
			this.oldSkyTexture = oldSkyTexture;
			this.oldLightColor = oldLightColor;
			this.oldWallsAlpha = oldWallsAlpha;
			this.groundColor = groundColor;
			this.groundTexture = groundTexture;
			this.skyColor = skyColor;
			this.skyTexture = skyTexture;
			this.lightColor = lightColor;
			this.wallsAlpha = wallsAlpha;
		}
		
		@Override
		public void undo() throws CannotUndoException
		{
			super.undo();
			doModify3DAttributes(this.home, this.oldGroundColor, this.oldGroundTexture, this.oldSkyColor,
					this.oldSkyTexture, this.oldLightColor, this.oldWallsAlpha);
		}
		
		@Override
		public void redo() throws CannotRedoException
		{
			super.redo();
			doModify3DAttributes(this.home, this.groundColor, this.groundTexture, this.skyColor, this.skyTexture,
					this.lightColor, this.wallsAlpha);
		}
		
		@Override
		public String getPresentationName()
		{
			return this.preferences.getLocalizedString(Home3DAttributesController.class, "undoModify3DAttributesName");
		}
	}
	
	/**
	 * Modifies the 3D attributes of the given <code>home</code>.
	 */
	private static void doModify3DAttributes(Home home, int groundColor, HomeTexture groundTexture, int skyColor,
			HomeTexture skyTexture, int lightColor, float wallsAlpha)
	{
		HomeEnvironment homeEnvironment = home.getEnvironment();
		homeEnvironment.setGroundColor(groundColor);
		homeEnvironment.setGroundTexture(groundTexture);
		homeEnvironment.setSkyColor(skyColor);
		homeEnvironment.setSkyTexture(skyTexture);
		homeEnvironment.setLightColor(lightColor);
		homeEnvironment.setWallsAlpha(wallsAlpha);
	}
}
